package com.dbflow5.query;

import com.dbflow5.StringUtils;
import com.dbflow5.sql.Query;

/**
 * Description: Rewritten from the ground up, this class makes it easier to build an alias.
 */
public class NameAlias implements Query {
    private final String name;
    private final String aliasName;
    public String tableName = null;
    public String keyword = null;
    public boolean shouldStripIdentifier = true;
    public boolean shouldStripAliasName = true;
    private boolean shouldAddIdentifierToQuery = true;
    private boolean shouldAddIdentifierToAliasName = true;

    public NameAlias(String name,
                     String aliasName,
                     String tableName,
                     String keyword,
                     boolean shouldStripIdentifier,
                     boolean shouldStripAliasName,
                     boolean shouldAddIdentifierToQuery,
                     boolean shouldAddIdentifierToAliasName){

        this.name = name;
        this.aliasName = aliasName;
        this.tableName = tableName;
        this.keyword = keyword;
        this.shouldStripIdentifier = shouldStripIdentifier;
        this.shouldStripAliasName = shouldStripAliasName;
        this.shouldAddIdentifierToQuery = shouldAddIdentifierToQuery;
        this.shouldAddIdentifierToAliasName = shouldAddIdentifierToAliasName;
    }

    /**
     * The name used in queries.
     * @return The name used in queries. If an alias is specified, use that, otherwise use the name
     * of the property with a table name (if specified).
     */
    public String getQuery(){
        if(StringUtils.isNotNullOrEmpty(aliasName)){
            return aliasName;
        }else if(StringUtils.isNotNullOrEmpty(name)){
            return fullName();
        }else {
            return "";
        }
    }

    /**
     * The value used as a key.
     * @return The value used as a key. Uses either the [.aliasNameRaw]
     * or the [.nameRaw], depending on what's specified.
     */
    public String nameAsKey() {
        if (StringUtils.isNotNullOrEmpty(aliasName)) {
            return aliasNameRaw();
        } else {
            return nameRaw();
        }
    }

    /**
     * The full query that represents itself with `{tableName}`.`{name}` AS `{aliasName}`
     * @return The full query that represents itself with `{tableName}`.`{name}` AS `{aliasName}`
     */
    public String fullQuery() {
        String query = fullName();
        if (StringUtils.isNotNullOrEmpty(aliasName)) {
            query += " AS "+aliasName();
        }
        if (StringUtils.isNotNullOrEmpty(keyword)) {
            query = keyword+ " "+ query;
        }
        return query;
    }

    private NameAlias(Builder builder) {
        this(builder.shouldStripIdentifier? StringUtils.stripQuotes(builder.name) == null? "" : StringUtils.stripQuotes(builder.name) : builder.name,
                builder.shouldStripAliasName? StringUtils.stripQuotes(builder.aliasName): builder.aliasName,
                StringUtils.isNotNullOrEmpty(builder.tableName)? StringUtils.quoteIfNeeded(builder.tableName) : null,
                builder.keyword,
                builder.shouldStripIdentifier,
                builder.shouldStripAliasName,
                builder.shouldAddIdentifierToQuery,
                builder.shouldAddIdentifierToAliasName);
    }

    /**
     * The real column name.
     * @return The real column name.
     */
    public String name() {
        if (StringUtils.isNotNullOrEmpty(name) && shouldAddIdentifierToQuery)
            return StringUtils.quoteIfNeeded(name);
        else return name;
    }

    /**
     * The name, stripped from identifier syntax completely.
     * @return The name, stripped from identifier syntax completely.
     */
    public String nameRaw() {
        return shouldStripIdentifier? name : StringUtils.stripQuotes(name) == null? "" : StringUtils.stripQuotes(name);
    }

    /**
     * The name used as part of the AS query.
     * @return The name used as part of the AS query.
     */
    public String aliasName() {
        if (StringUtils.isNotNullOrEmpty(aliasName) && shouldAddIdentifierToAliasName)
            return StringUtils.quoteIfNeeded(aliasName);
        else return aliasName;
    }

    /**
     * The alias name, stripped from identifier syntax completely.
     * @return The alias name, stripped from identifier syntax completely.
     */
    public String aliasNameRaw() {
        return shouldStripAliasName? aliasName : StringUtils.stripQuotes(aliasName);
    }

    /**
     * The `{tableName}`.`{name}`. If [.tableName] specified.
     * @return The `{tableName}`.`{name}`. If [.tableName] specified.
     */
    public String fullName() {
        return (StringUtils.isNotNullOrEmpty(tableName)? tableName + "." : "") + name();
    }

    @Override
    public java.lang.String toString() {
        return fullQuery();
    }

    /**
     * Constructs a builder as a new instance that can be modified without fear.
     * @return Constructs a builder as a new instance that can be modified without fear.
     */
    public Builder newBuilder() {
        return new Builder(name)
            .keyword(keyword)
            .as(aliasName)
            .shouldStripAliasName(shouldStripAliasName)
            .shouldStripIdentifier(shouldStripIdentifier)
            .shouldAddIdentifierToName(shouldAddIdentifierToQuery)
            .shouldAddIdentifierToAliasName(shouldAddIdentifierToAliasName)
            .withTable(tableName);
    }

    public <T> Operator<T> op() {
        return Operator.op(this);
    }

    public static class Builder {
        String name;
        String aliasName = null;
        String tableName = null;
        boolean shouldStripIdentifier = true;
        boolean shouldStripAliasName = true;
        boolean shouldAddIdentifierToQuery = true;
        boolean shouldAddIdentifierToAliasName = true;
        String keyword = null;

        public Builder(String name){
            this.name = name;
        }

        /**
         * Appends a DISTINCT that prefixes this alias class.
         * @return this
         */
        public Builder distinct() {
            return keyword("DISTINCT");
        }

        /**
         * Appends a keyword that prefixes this alias class.
         * @param keyword keyword
         * @return this
         */
        public Builder keyword(String keyword) {
            this.keyword = keyword;
            return this;
        }

        /**
         * Provide an alias that is used `{name}` AS `{aliasName}`
         * @param aliasName aliasName
         * @return this
         */
        public Builder as(String aliasName) {
            this.aliasName = aliasName;
            return this;
        }

        /**
         * Provide a table-name prefix as such: `{tableName}`.`{name}`
         * @param tableName tableName
         * @return this
         */
        public Builder withTable(String tableName) {
            this.tableName = tableName;
            return this;
        }

        /**
         * Builder with shouldStripIdentifier
         * @param shouldStripIdentifier If true, we normalize the identifier [.name] from any
         * ticks around the name. If false, we leave it as such.
         * @return this
         */
        public Builder shouldStripIdentifier(boolean shouldStripIdentifier) {
            this.shouldStripIdentifier = shouldStripIdentifier;
            return this;
        }

        /**
         * Builder with shouldStripAliasName
         * @param shouldStripAliasName If true, we normalize the identifier [.aliasName] from any
         * ticks around the name. If false, we leave it as such.
         * @return this
         */
        public Builder shouldStripAliasName(boolean shouldStripAliasName) {
            this.shouldStripAliasName = shouldStripAliasName;
            return this;
        }

        /**
         * Builder with shouldAddIdentifierToAliasName
         * @param shouldAddIdentifierToName If true (default), we add the identifier to the name: `{name}`
         * @return this
         */
        public Builder shouldAddIdentifierToName(boolean shouldAddIdentifierToName) {
            this.shouldAddIdentifierToQuery = shouldAddIdentifierToName;
            return this;
        }

        /**
         * Builder with shouldAddIdentifierToAliasName
         * @param shouldAddIdentifierToAliasName If true (default), we add an identifier to the alias
         * name. `{aliasName}`
         * @return this
         */
        public Builder shouldAddIdentifierToAliasName(boolean shouldAddIdentifierToAliasName) {
            this.shouldAddIdentifierToAliasName = shouldAddIdentifierToAliasName;
            return this;
        }

        public NameAlias build() {
            return new NameAlias(this);
        }

    }

    /**
     * Combines any number of names into a single [NameAlias] separated by some operation.
     *
     * @param operation The operation to separate into.
     * @param names     The names to join.
     * @return The new namealias object.
     */
    public static NameAlias joinNames(String operation, String... names) {
        StringBuilder newName = new StringBuilder();
        for (int i=0;i< names.length;i++) {
            if (i > 0) {
                newName.append(" ").append(operation).append(" ");
            }
            newName.append(names[i]);
        }
        return rawBuilder(newName.toString()).build();
    }

    public static Builder builder(String name) {
        return new Builder(name);
    }

    public static Builder tableNameBuilder(String tableName) {
        return new Builder("")
                .withTable(tableName);
    }

    /**
     * A new instance without adding identifier `` to any part of the query.
     * @param name The raw name of this alias.
     * @return A new instance without adding identifier `` to any part of the query.
     */
    public static Builder rawBuilder(String name) {
        return new Builder(name)
                .shouldStripIdentifier(false)
                .shouldAddIdentifierToName(false);
    }

    public static NameAlias of(String name) {
        return builder(name).build();
    }

    public static NameAlias of(String name, String aliasName) {
        return builder(name).as(aliasName).build();
    }

    public static NameAlias ofTable(String tableName, String name) {
        return builder(name).withTable(tableName).build();
    }

    public static NameAlias nameAlias(String name) {
        return NameAlias.of(name);
    }

    public static NameAlias as(String name, String alias) {
        return NameAlias.of(name, alias);
    }

}


